About Sharding and MSA
π§ μλ¬Έβ
λ§μ νκ³Ό μ‘°μ§μ΄ λ°μ΄ν°λ² μ΄μ€μ μ±λ₯ λ³λͺ©μ΄λ λμ©λ μ²λ¦¬ μ΄μμ μ§λ©΄νμ λ, κ°μ₯ λ¨Όμ λ μ¬λ¦¬λ ν΄κ²°μ±
μ€ νλκ° λ°λ‘ **μ€λ©(Sharding)**μ
λλ€.
μ€λ©μ λΆλͺ
ν κ°λ ₯ν μν νμ₯ μ λ΅μ΄μ§λ§, κ·Έλ§νΌ λμ
κ³Ό μ΄μμ λ°λ₯΄λ 볡μ‘λμ μνμ±λ ν½λλ€.
νΉν, μ€λ©μ λ¨μν κΈ°μ μ κΈ°λ₯μ΄ μλλΌ μ 체 μμ€ν
μν€ν
μ²μ μν₯μ μ£Όλ ꡬ쑰μ κ²°μ μ΄κΈ° λλ¬Έμ,
λμ
μ μλλ₯΄κΈ°λ³΄λ€λ μ€λ© μμ΄ ν΄κ²°ν μ μλ λ°©μλ€μ λ¨Όμ κ³ λ €νκ³ ,
μ€λ©μ΄ νμν μμ κ³Ό λ²μλ₯Ό λͺ
νν νλ¨ν νμ μ μ©νλ κ²μ΄ λ°λμ§ν©λλ€.
μ΄ κΈμμλ μ€λ© λμ
μ κ³ λ €ν μ μλ λ¨κ³,
μ€λ©μ μ μ© μμΉ,
MSA λ° vertical slicingκ³Όμ κ΄κ³,
κ·Έλ¦¬κ³ κΆκ·Ήμ μΌλ‘ λλ
Έ μλΉμ€ μν€ν
μ²μ BFF ꡬ쑰λ‘μ νμ₯κΉμ§ νλκ² λ€λ€λ΄
λλ€.
β μ€λ© λμ μ κ³ λ € μ¬νβ
1. MMM ꡬμ±β
κ°μ₯ λ¨Όμ κ³ λ €ν μ μλ νμ₯ λ°©μμ Master-Replica(MMM) ꡬμ±μ
λλ€.
νλμ Master DBμ μ¬λ¬ κ°μ Read Replicaλ₯Ό λΆμ¬μ μ½κΈ° λΆνλ₯Ό λΆμ°ν μ μμ΅λλ€.
- μ°κΈ°λ μ€μ§ Masterμμλ§ λ°μ
- μ½κΈ°λ Replicaμμ μ²λ¦¬
- Spring Bootμμλ
AbstractRoutingDataSource
λ±μ ν΅ν΄ read/write λΆλ¦¬λ₯Ό μ½κ² ꡬν κ°λ₯ - Replica Lag κ³ λ € νμ
μ΄ κ΅¬μ‘°λ§μΌλ‘λ λλΆλΆμ μλΉμ€λ μ΄λΉ μμ² QPS μμ€μ μ²λ¦¬ μ±λ₯μ ν보ν μ μμ΅λλ€.
2. ν μ΄λΈ νν°μ λβ
DBMS μ°¨μμμ μ§μνλ μν νν°μ
λλ μ’μ μ λ΅μ
λλ€.
μλ₯Ό λ€μ΄ PostgreSQLμ table partition
, MySQLμ partition by range
λ±μ νμ©ν΄ λμ©λ ν
μ΄λΈμ λλ μ μμ΅λλ€.
- λ μ§ κΈ°μ€ νν°μ (μ: μλ³ μ£Όλ¬Έ ν μ΄λΈ)
- μ§μ/κ΅κ° μ½λ κΈ°μ€ νν°μ
- μΈλ±μ€ λ° I/O μ΅μ νμ ν¨κ³Όμ
λ¨μ μ:
- νν°μ κ° μ‘°μΈμ΄ μ΄λ €μ
- νν°μ κ΄λ¦¬ μ μ± νμ (drop/recreate)
νν°μ λλ§μΌλ‘λ μ±λ₯ λ³λͺ©μ΄ μνλλ κ²½μ°κ° λ§μΌλ©°, μ€λ© μ΄μ μ λ°λμ κ³ λ €ν΄μΌ ν λ¨κ³μ λλ€.
β μ€λ© ꡬ쑰μ ν΅μ¬β
μ€λ©μ κΈ°λ³Έ κ°λ μ "νλμ ν μ΄λΈμ μ¬λ¬ DB μΈμ€ν΄μ€λ‘ λλμ΄ μ μ₯"νλ κ²μ λλ€.
1. μ€λ ν€ μ μ β
μ€λ ν€λ λ°μ΄ν°λ₯Ό μ΄λ€ κΈ°μ€μΌλ‘ λλμ§λ₯Ό κ²°μ νλ ν΅μ¬μ
λλ€.
μ’μ μ€λ ν€λ λ€μ 쑰건μ λ§μ‘±ν΄μΌ ν©λλ€:
- κ±°μ λͺ¨λ 쿼리μ ν¬ν¨λμ΄μΌ ν¨
- κ· λ±νκ² λΆν¬λμ΄μΌ ν¨
- λ³κ²½λμ§ μμμΌ ν¨
λνμ μΈ ν€: user_id
, tenant_id
, contract_id
, region_id
2. μ€λ κ²°μ λ°©μβ
- Mod λ°©μ:
shardId = user_id % N
- Hash Slot λ°©μ:
slot = hash(user_id) % 1024 β slotToShardMap
- Range λ°©μ:
user_id
κ°μ λ²μλ‘ λΆκΈ°
μ€λ κ²°μ μ λ³νμ§ μλλ‘ κ³ μ λ μκ³ λ¦¬μ¦ λλ ν μ΄λΈ κΈ°λ°μΌλ‘ κ΄λ¦¬ν΄μΌ ν¨
3. λΌμ°ν ꡬν λ°©μβ
- μ ν리μΌμ΄μ
λ 벨 λΌμ°ν
:
user_id
κΈ°λ°μΌλ‘ μ§μ 컀λ₯μ μ ν - λ―Έλ€μ¨μ΄ κΈ°λ°: ProxySQL, Vitess λ±μμ 쿼리λ₯Ό ν΄μνκ³ μλ λΆκΈ°
- μ‘°ν©ν: λΌμ°ν
ν
μ΄λΈμ μ ν리μΌμ΄μ
μμ λμ μΌλ‘ λ‘λ© (
slot β shard
λ§΅ν)
β μ€λ©μ΄ νμν μμ β
μμΉ κΈ°μ€β
νλͺ© | κΈ°μ€ |
---|---|
QPS | 5,000~10,000 μ΄μ |
TPS | μ΄λΉ μ°κΈ° 1,000건 μ΄μ |
ν μ΄λΈ μ¬μ΄μ¦ | λ¨μΌ ν μ΄λΈ 100~200GB μ΄μ |
컀λ₯μ μ | 500~1000 μ΄μ μμ μ μ§ |
μΈλ±μ€ λ©λͺ¨λ¦¬ μ μ¬ μ€ν¨ | νμ΄μ§ μΊμ ννΈμ¨ μ ν |
μ€λ© κ΅¬μ± μμμ Spring κΈ°λ° Kotlin μ½λβ
π μ€λ© κ΅¬μ± μμ (2κ° μ€λ)β
- κΈ°μ€:
user_id % 2
- μ€λ:
shard1
: user_id μ§μshard2
: user_id νμ
+--------------------+
| Application API |
+--------------------+
|
ShardRouter
|
+-------------+-------------+
| |
shard1(DBClient) shard2(DBClient)
β Kotlin + Coroutine κΈ°λ° μ€λ© λΌμ°ν μμβ
πΉ Slot κΈ°λ° λΌμ°ν°β
data class ShardInfo(val id: String, val client: DatabaseClient)
class ShardRouter(private val shards: List<ShardInfo>) {
fun route(userId: Long): DatabaseClient {
val shardIndex = (userId % shards.size).toInt()
return shards[shardIndex].client
}
}
πΉ μλΉμ€μμ λΌμ°ν μ μ©β
class UserService(private val router: ShardRouter) {
suspend fun getUser(userId: Long): User? {
val client = router.route(userId)
return client.sql("SELECT * FROM users WHERE user_id = :id")
.bind("id", userId)
.map { row -> User.from(row) }
.one()
.awaitFirstOrNull()
}
}
πΉ CoroutineContext κΈ°λ° μ λ¬ μμβ
data class ShardContext(val userId: Long) : CoroutineContext.Element {
companion object Key : CoroutineContext.Key<ShardContext>
override val key = Key
}
suspend fun <T> withShard(userId: Long, block: suspend () -> T): T {
return withContext(ShardContext(userId)) {
block()
}
}
β μ΄ν ShardRouter
μμ coroutineContext[ShardContext]?.userId
λ‘ μΆμΆ κ°λ₯
β Spring + AbstractRoutingDataSource (JDBC λ°©μ)β
object ShardContextHolder {
private val context = ThreadLocal<Long?>()
fun setUserId(id: Long) = context.set(id)
fun getUserId(): Long? = context.get()
fun clear() = context.remove()
}
class ShardRoutingDataSource(...) : AbstractRoutingDataSource() {
override fun determineCurrentLookupKey(): Any? {
val userId = ShardContextHolder.getUserId() ?: return "default"
return "shard${userId % 2}"
}
}
β AOPλ‘ userId
κ°μ μ¬μ μ μΈν
νμ¬ @Transactional
μ¬μ© κ°λ₯νκ² μ²λ¦¬
βοΈ κ²°λ‘ β
- μ€λ© λΌμ°ν
μ Kotlin Coroutine νκ²½μμ
CoroutineContext
λ₯Ό, Spring JDBC νκ²½μμAbstractRoutingDataSource
λ₯Ό κΈ°λ°μΌλ‘ μ€κ³ κ°λ₯ - μ€λ© λΌμ°ν°λ κ°λ₯ν λ²μ© κ΅¬μ‘°λ‘ μΆμννμ¬ μλΉμ€μ μ μ°νκ² μ£Όμ